package com.zwh.se.string;

import java.lang.reflect.Field;
import java.util.Arrays;

/**
 * @author yexi
 * 不可变字符串：String
 * 可变字符串：StringBuilder，StringBuffer
 */
public class StringBuilderDemo {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchFieldException {

        System.out.println("--------------------[0]StringBuilder的原理--------------------");
        //定义一个可变字符串对象
        StringBuilder sb1 = new StringBuilder(100).append("asd");
        //通过反射获取到对象属性信息
        //可以知道我们传入的100实际上是对value数组进行初始化，长度为你传入的数字
        getMsg(sb1);
        //虽然表面上只调用StringBuilder的空构造器,但是点进去可以看到实际底层做了一个对value数组的初始化，长度为16
        StringBuilder sb2 = new StringBuilder();
        getMsg(sb2);
        //StringBuilder的增加方法以及链式操作的展示
        //链式操作：本质上就是在方法调用完后,又将原对象返回,从而达到不需要根据返回值一行行地写,直接可以一行做完很多事情
        sb2.append("def").append("aaaaaaaa").append("bbb").append("ooooooo");
        //扩容原理:可以通过观看append源码得知。从上面分析我们可以知道了value数组的初始化,但如果在append时超过了原数组的长度,就会触发扩容机制。
        /**
         * @see AbstractStringBuilder#expandCapacity(int)
         * int newCapacity = value.length * 2 + 2;
         * value = Arrays.copyOf(value, newCapacity);
         */
        //所谓扩容,本质上是创建一个新的数组,然后新数组初始化的长度=原数组的长度 * 2 + 2,然后将原本的元素都复制过去的意思
        getMsg(sb2);
        //这就是所谓可变的概念,它与不可变最大的区别就在于StringBuilder这个对象的地址是不变的
        System.out.println(sb1.append("abc") == sb1.append("def"));//true

        System.out.println("--------------------[1]StringBuilder的方法演示-------------------");
        StringBuilder sb = new StringBuilder();

        //增
        sb.append("abcdefghijkl");
        System.out.println(sb);

        //删
        //删除下标在[start,end)上的字符
        sb.delete(0, 1);
        System.out.println(sb);
        //删除索引为在index上的字符
        sb.deleteCharAt(0);
        System.out.println(sb);

        //改
        //在下标为offset的位置上插入指定元素
        sb.insert(1, "a");
        System.out.println(sb);
        //将下标在[start,end)上的字符替换为提供的元素
        sb.replace(3, 5, "hehe");
        System.out.println(sb);
        //将指定索引位index替换为指定字符
        sb.setCharAt(0, 'b');
        System.out.println(sb);

        //查
        //返回指定索引位的字符
        System.out.println(sb.charAt(0));
        //从左往右,查询指定字符串第一次出现的索引位
        System.out.println(sb.indexOf("hehe"));
        //从右往左,查询指定字符串第一次出现的索引位
        System.out.println(sb.lastIndexOf("hehe"));
        //获取目前底层value数组实际被使用的长度
        System.out.println(sb.length());
        //获取目前底层value数组的实际长度
        System.out.println(sb.capacity());

        //截取
        //截取[start,end)的字符并返回的一个新的String,该方法不会影响StringBuilder
        String str = sb.substring(2, 4);
        System.out.println(str);

        //TIPS:StringBuffer与StringBuilder几乎完全一样,只是StringBuffer的各个方法都加了synchronized修饰以保证线程安全,就不再多做演示
    }

    static void getMsg(StringBuilder sb) throws NoSuchFieldException, IllegalAccessException {
        //接下来需要认识StringBuilder两个非常重要的属性
        Field value = sb.getClass().getSuperclass().getDeclaredField("value");
        value.setAccessible(true);
        //可见value就是StringBuilder底层实际存储的数组
        System.out.println("现在的StringBuilder底层存储的是" + Arrays.toString((char[]) value.get(sb)));
        //capacity()获取的是底层value.length
        System.out.println("长度是" + sb.capacity());
        //length()获取的是count的值,可见count就是value数组中实际被使用的长度
        System.out.println("现在的StringBuilder底层存储的是实际使用长度是" + sb.length());
    }

    /*
        总结：String、StringBuffer、StringBuilder区别与联系
        1、String类是不可变类，即一旦一个String对象被创建后，包含在这个对象中的字符序列是不可改变的，直至这个对象销毁。
        2、StringBuffer类是JDK1.0即存在的代表一个字符序列可变的字符串类，可以通过append、insert、reverse、setChartAt、setLength等方法改变其内容。
           我们在完成我们的业务需求后，通过调用toString方法将其转变为String。StringBuffer是线程安全的(方法均被synchronized修饰)
        3、StringBuffer为了保证线程安全的synchronized,会导致性能受损。因此于JDK1.5中新增了StringBuilder类，它基本等同于StringBuffer，构造方法和方法基本相同。
          不同是StringBuilder是线程不安全的，所以性能略高。通常情况下，创建一个内容可变的字符串，应该优先考虑使用StringBuilder
     */
}
